package logger

import (
	"fmt"
	"os"
	"strconv"
	"time"
)

type FileLogger struct {
	level                 int
	logPath               string
	logName               string
	file                  *os.File
	warnFile              *os.File
	logDataChan           chan *LogData
	logRotateType         int
	logRotateSizeTypeSize int64
	fileCurrentHour       int
	warnfileCurrentHour   int
}

func NewFileLogger(config map[string]string) (logger Logger, err error) {
	logPath, ok := config["log_path"]
	if !ok {
		err = fmt.Errorf("not found log_path")
		return
	}
	logName, ok := config["log_name"]
	if !ok {
		err = fmt.Errorf("not found log_name")
		return
	}
	logLevel, ok := config["log_level"]
	if !ok {
		err = fmt.Errorf("not found log_level")
		return
	}
	level := getLogLevel(logLevel)
	logChanSize, ok := config["log_chan"]
	if !ok {
		logChanSize = "50000"
	}
	logChanSizeNum, err := strconv.Atoi(logChanSize)
	if err != nil {
		logChanSizeNum = 50000
	}
	var logRotateType = LogRotateTypeHour
	var logRotateSize int64
	logRotateTypeStr, ok := config["log_rotate_type"]
	if !ok {
		logRotateType = LogRotateTypeHour
	} else {
		if logRotateTypeStr == "size" {
			logRotateType = LogRotateTypeSize
			logRotateSizeStr, ok := config["log_rotate_size"]
			if !ok {
				logRotateSizeStr = "104857600" // 100M
			}
			logRotateSize, err = strconv.ParseInt(logRotateSizeStr, 10, 64)
			if err != nil {
				logRotateSize = 104857600
			}
			if logRotateSize < 104857600 {
				logRotateSize = 104857600
			}
		} else {
			logRotateType = LogRotateTypeHour
		}
	}
	logger = &FileLogger{
		level:                 level,
		logPath:               logPath,
		logName:               logName,
		logDataChan:           make(chan *LogData, logChanSizeNum),
		logRotateType:         logRotateType,
		logRotateSizeTypeSize: logRotateSize,
		fileCurrentHour:       time.Now().Hour(),
		warnfileCurrentHour:   time.Now().Hour(),
	}
	logger.Init()
	return
}

func (f *FileLogger) Init() {
	f.SetLevel(f.level)

	fileName := fmt.Sprintf("%s%s%s.log", f.logPath, string(os.PathSeparator), f.logName)
	file, err := os.OpenFile(fileName, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755)
	if err != nil {
		panic(fmt.Sprintf("Open log file %s error:%v", fileName, err))
	}
	f.file = file

	warFileName := fmt.Sprintf("%s%s%s.warn", f.logPath, string(os.PathSeparator), f.logName)
	warFile, err := os.OpenFile(warFileName, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755)
	if err != nil {
		panic(fmt.Sprintf("Open log file %s error:%v", warFileName, err))
	}
	f.warnFile = warFile
	go f.writeLogBackGround()
}

func (f *FileLogger) writeLogBackGround() {
	for logData := range f.logDataChan {
		f.handleRotateFile(logData.WarnAndFatal)
		if logData.WarnAndFatal {
			fmt.Fprintf(f.warnFile, "%s [%s] (%s %s:%d) %s\n", logData.TimeString, logData.LevelString,
				logData.FileName, logData.FuncName, logData.LineNum, logData.Message)
		} else {
			fmt.Fprintf(f.file, "%s [%s] (%s %s:%d) %s\n", logData.TimeString, logData.LevelString,
				logData.FileName, logData.FuncName, logData.LineNum, logData.Message)
		}
	}
}

func (f *FileLogger) handleRotateFile(isWarnFile bool) {
	if f.logRotateType == LogRotateTypeHour {
		f.rotateFileByHour(isWarnFile)
	} else {
		f.rotateFileBySize(isWarnFile)
	}
}

func (f *FileLogger) rotateFileByHour(isWarnFile bool) (err error) {
	now := time.Now()
	if isWarnFile {
		if now.Hour() > f.warnfileCurrentHour {
			newFile, err := f.roteFile(isWarnFile, f.warnFile)
			if err != nil {
				return err
			}
			f.warnFile = newFile
			f.warnfileCurrentHour = now.Hour()
		}
	} else {
		if now.Hour() > f.warnfileCurrentHour {
			newFile, err := f.roteFile(isWarnFile, f.file)
			if err != nil {
				return err
			}
			f.file = newFile
			f.fileCurrentHour = now.Hour()
		}
	}

	return
}

func (f *FileLogger) rotateFileBySize(isWarnFile bool) (err error) {
	if isWarnFile {
		fileState, err := f.warnFile.Stat()
		if err != nil {
			return err
		}
		fileSize := fileState.Size()
		if fileSize > f.logRotateSizeTypeSize {
			newFile, err := f.roteFile(isWarnFile, f.warnFile)
			if err != nil {
				return err
			}
			f.warnFile = newFile
		}
	} else {
		fileState, err := f.file.Stat()
		if err != nil {
			return err
		}
		fileSize := fileState.Size()
		if fileSize > f.logRotateSizeTypeSize {
			newFile, err := f.roteFile(isWarnFile, f.file)
			if err != nil {
				return err
			}
			f.file = newFile
		}
	}
	return
}

func (f *FileLogger) roteFile(isWarnFile bool, file *os.File) (newfile *os.File, err error) {
	fileName := fmt.Sprintf("%s%s%s.log", f.logPath, string(os.PathSeparator), f.logName)
	if isWarnFile {
		fileName = fmt.Sprintf("%s%s%s.warn", f.logPath, string(os.PathSeparator), f.logName)
	}
	now := time.Now()
	timeString := now.Format("2006-01-02-15-04-05")
	fileNameBack := fmt.Sprintf("%s-%s", fileName, timeString)
	ok, err := PathExists(fileNameBack)
	if ok {
		ok, err = PathExists(fileName)
		if ok {
			return
		}
	}
	file.Close()
	os.Rename(fileName, fileNameBack)
	newfile, err = os.OpenFile(fileName, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755)
	return
}

func (f *FileLogger) SetLevel(level int) {
	if level < LogLevelDebug || level > LogLevelFatal {
		f.level = LogLevelDebug
		return
	}
	f.level = level
}

func (f *FileLogger) GetLevel() int {
	return f.level
}

func (f *FileLogger) Debug(format string, args ...interface{}) {
	if f.level > LogLevelDebug {
		return
	}
	logData := writeLog(LogLevelDebug, format, args...)
	select {
	case f.logDataChan <- logData:
	default:
	}
}

func (f *FileLogger) Trace(format string, args ...interface{}) {
	if f.level > LogLevelTrace {
		return
	}
	logData := writeLog(LogLevelTrace, format, args...)
	select {
	case f.logDataChan <- logData:
	default:
	}
}

func (f *FileLogger) Info(format string, args ...interface{}) {
	if f.level > LogLevelInfo {
		return
	}
	logData := writeLog(LogLevelInfo, format, args...)
	select {
	case f.logDataChan <- logData:
	default:
	}
}

func (f *FileLogger) Warn(format string, args ...interface{}) {
	if f.level > LogLevelWarn {
		return
	}
	logData := writeLog(LogLevelWarn, format, args...)
	select {
	case f.logDataChan <- logData:
	default:
	}
}

func (f *FileLogger) Error(format string, args ...interface{}) {
	if f.level > LogLevelError {
		return
	}
	logData := writeLog(LogLevelError, format, args...)
	select {
	case f.logDataChan <- logData:
	default:
	}
}

func (f *FileLogger) Fatal(format string, args ...interface{}) {
	if f.level > LogLevelFatal {
		return
	}
	logData := writeLog(LogLevelFatal, format, args...)
	select {
	case f.logDataChan <- logData:
	default:
	}
}

func (f *FileLogger) Close() {
	f.file.Close()
	f.warnFile.Close()
}
